﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.Serialization;
using System.Data.Common;
using System.Windows.Forms;
using System.Reflection;
using System.IO;
using MacomberMapSystem.Common.Internals;
using MacomberMapSystem.Common.Integration;
using MacomberMapSystem.Common.Database;
using System.Drawing;
using System.Xml;
using MacomberMapSystem.Common.User_Interfaces.One_Lines;

namespace MacomberMapSystem.Common.CIM
{
    /// <summary>
    /// This class holds information on a CIM element
    /// </summary>
    public class CIM_Element : MM_Serializable, IComparable<CIM_Element>, IEquatable<CIM_Element>, IComparable, IFormattable
    {
        #region Variable declarations
        /// <summary>Whether the telephone number should be exported</summary>
        public static bool WritePhone = false;

        /// <summary>Whether the DUNS number should be exported</summary>
        public static bool WriteDUNS = true;

        /// <summary>The CIM connector associated with this element</summary>
        public MM_Database_Model CIM;

        /// <summary>Our placeholder for the item's name, so we only have to load it once// </summary>
        private string _Name = null;

        /// <summary>The rdf:ID of the element</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public CIM_RdfID rdfID;

        /// <summary>The type of the element</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public Byte Type;

        /// <summary>The checksum of the element</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public Byte[] CheckSum;


        /// <summary>The name of the element type</summary>
        public virtual String TypeName
        {
            get { return CIM.Types[Type]; }
            set
            {
                for (int a = 0; a < CIM.Types.Count; a++)
                    if (CIM.Types[a] == value)
                    {
                        Type = (byte)a;
                        return;
                    }
            }
        }

        /// <summary>
        /// Retrieve connected elements of a type
        /// </summary>
        /// <param name="TypeName"></param>
        /// <returns></returns>
        public virtual CIM_Element[] ElementsOfType(String TypeName)
        {
            List<CIM_Element> OutElems = new List<CIM_Element>();
            foreach (CIM_Element Elem in Elements)
                if (Elem.TypeName == TypeName)
                    OutElems.Add(Elem);
            return OutElems.ToArray();
        }

        /// <summary>
        /// Retrieve connected elements of a type
        /// </summary>
        /// <param name="ElemType"></param>
        /// <returns></returns>
        public virtual CIM_Element[] ElementsOfType(MM_OneLine_Element.enumElemTypes ElemType)
        {
            List<CIM_Element> OutElems = new List<CIM_Element>();
            foreach (CIM_Link VoltageLevel in InLinks)
                if (VoltageLevel.TypeName == "cim:VoltageLevel")
                    foreach (CIM_Link Elem in InLinks)
                        if (Elem.ElemType == ElemType)
                            OutElems.Add(Elem.Element);
            return OutElems.ToArray();
        }



        /// <summary>
        /// Determine the element types within a substation
        /// </summary>
        public virtual MM_OneLine_Element.enumElemTypes ElementTypes
        {
            get
            {
                MM_OneLine_Element.enumElemTypes OutResp = MM_OneLine_Element.enumElemTypes.None;
                foreach (CIM_Link VoltageLevel in this.InLinks)
                    if (VoltageLevel.TypeName == "cim:VoltageLevel")
                        foreach (CIM_Link Elem in VoltageLevel.Element.InLinks)
                            OutResp &= Elem.ElemType;
                return OutResp;
            }
        }

        /// <summary>
        /// The element type for this element based on our configuration parameters
        /// </summary>
        public String ElemType
        {
            get
            {
                if (TypeName == "cim:Disconnector")
                    return "Switch";
                else if (TypeName == "cim:GroundDisconnector")
                    return "GroundSwitch";
                else if (TypeName == "cim:ACLineSegment")
                    return "Line";
                else if (TypeName == "cim:SeriesCompensator")
                {
                    Dictionary<String, String> XmlVals = this.Attributes;
                    double r = Convert.ToDouble(XmlVals["cim:SeriesCompensator.r"]);
                    double x = Convert.ToDouble(XmlVals["cim:SeriesCompensator.x"]);
                    if (r > Math.Abs(x / 100))
                        if (x > 0)
                            return "ResistorReactor";
                        else if (x < 0)
                            return "ResistorCapacitor";
                        else
                            return "Resistor";
                    else if (x > 0)
                        return "Reactor";
                    else if (x < 0)
                        return "Capacitor";
                    else
                        return "Reactor";
                    // throw new Exception("Series compensators must have resistance or reactance!");
                }
                else if (TypeName == "cim:Breaker")
                    return "Breaker";
                else if (TypeName == "cim:PowerTransformer")
                    return "Transformer";
                else if (TypeName == "cim:SynchronousMachine")
                    return "Unit";
                else if (TypeName == "etx:EndCap")
                    return "EndCap";
                else if (TypeName == "etx:PricingVector")
                    return "PricingVector";
                else if (TypeName == "cim:ConnectivityNode")
                    return "Node";
                else if (TypeName.EndsWith("StaticVarCompensator"))
                    return "StaticVarCompensator";
                else if (TypeName.EndsWith("Load"))
                    if (ParseSingle("LoadResource", null) == null)
                        return "Load";
                    else
                        return "LAAR";
                else if (TypeName == "cim:ShuntCompensator")
                    if (Convert.ToDouble(this["NominalMVAR"]) > 0)
                        return "Capacitor";
                    else
                        return "Reactor";
                else if (TypeName == "etx:SettlementLoadZone")
                    return "LoadZone";
                else if (TypeName == "cim:SubGeographicalRegion")
                    return "WeatherZone";
                else
                {
                    String[] OutType = TypeName.Split(':');
                    return OutType[OutType.Length - 1];
                }
            }
        }

        /// <summary>The starting position of the element</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public long Start;

        /// <summary>The length of the element</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public int Length;

        /// <summary>Incoming links</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public virtual CIM_Link[] InLinks
        {
            get {
                if (CIM.ModelChanges.Count == 0)
                    return _InLinks;
                else
                {
                    //First create our list of incoming links from our CIM file
                    Dictionary<CIM_RdfID, CIM_Link> TempLinks = new Dictionary<CIM_RdfID, CIM_Link>();
                    foreach (CIM_Link Link in _InLinks)
                        if (!TempLinks.ContainsKey(Link.rdfID) && !TempLinks.ContainsKey(Link.rdfID))
                            TempLinks.Add(Link.rdfID, Link);

                    //Look to see what link changes we have
                    foreach (CIM_Element_Difference cDiff in CIM.ModelChanges.Values)
                    {
                        if (cDiff.RemovedLinks != null && cDiff.RemovedLinks.Contains(rdfID))
                            TempLinks.Remove(cDiff.rdfID);
                        if (cDiff.AddedLinks.Contains(rdfID) && !TempLinks.ContainsKey(cDiff.rdfID))
                            TempLinks.Add(cDiff.rdfID, CIM_Link.FromElement(CIM.FromRdfID(cDiff.rdfID)));
                    }
                    return TempLinks.Values.ToArray();
                }
            }
            set { _InLinks = value; }
        }

        /// <summary>
        /// Return our collection of incoming links by attribute name
        /// </summary>
        public Dictionary<CIM_Element, String> InLinksByAttributeName
        {
            get
            {
                Dictionary<CIM_Element, String> OutDic = new Dictionary<CIM_Element, string>();
                foreach (CIM_Link InLink in InLinks)
                {
                    CIM_Element Elem = InLink.Element;
                    foreach (KeyValuePair<String, String> kvp in Elem.AttributeList)
                        if (kvp.Value == "#" + rdfID.ToString())
                            if (OutDic.ContainsKey(Elem))
                                OutDic[Elem] += "," + kvp.Key;
                            else
                                OutDic.Add(Elem, kvp.Key);
                }
                return OutDic;
            }
        }

        /// <summary>Outgoing links</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public virtual CIM_Link[] OutLinks
        {
            get
            {
                if (CIM.ModelChanges.Count == 0)
                    return _OutLinks;
                else
                {
                    //First create our list of outgoing links from our CIM file
                    Dictionary<CIM_RdfID, CIM_Link> TempLinks = new Dictionary<CIM_RdfID, CIM_Link>();
                    foreach (CIM_Link Link in _OutLinks)
                        if (!TempLinks.ContainsKey(Link.rdfID))
                            TempLinks.Add(Link.rdfID, Link);

                    //Look to see what link changes we have
                    CIM_Element_Difference cDiff;
                    if (CIM.ModelChanges.TryGetValue(rdfID, out cDiff))
                    {
                        if (cDiff.RemovedLinks != null)
                            foreach (CIM_RdfID ToRemove in cDiff.RemovedLinks)
                                TempLinks.Remove(ToRemove);
                        CIM_Element FoundElem;
                        if (cDiff.AddedLinks != null)
                            foreach (CIM_RdfID ToAdd in cDiff.AddedLinks)
                                if (!TempLinks.ContainsKey(ToAdd) && (FoundElem = CIM.FromRdfID(ToAdd)) != null)
                                    TempLinks.Add(ToAdd, CIM_Link.FromElement(FoundElem));
                    }

                    return TempLinks.Values.ToArray();
                }
            }
            set {_OutLinks = value; }
        }

        /// <summary>
        /// Return our collection of incoming links by attribute name
        /// </summary>
        public Dictionary<CIM_Element, String> OutLinksByAttributeName
        {
            get
            {
                Dictionary<CIM_Element, String> OutDic = new Dictionary<CIM_Element, string>();
                foreach (CIM_Link OutLink in _OutLinks)
                {
                    CIM_Element Elem = OutLink.Element;
                    foreach (KeyValuePair<String, String> kvp in AttributeList)
                        if (kvp.Value == "#" + Elem.rdfID.ToString())
                            if (OutDic.ContainsKey(Elem))
                                OutDic[Elem] += "," + kvp.Key;
                            else
                                OutDic.Add(Elem, kvp.Key);
                }
                return OutDic;
            }
        }
          
        private CIM_Link[] _InLinks = null, _OutLinks = null;

        /// <summary>If a substation, the bounds of the county</summary>
        public MM_Boundary County;

        /// <summary>Links in either direction</summary>
        public virtual CIM_Link[] Links
        {
            get
            {
                CIM_Link[] InLinks = this.InLinks;
                CIM_Link[] OutLinks = this.OutLinks;
                CIM_Link[] Resp = new CIM_Link[InLinks.Length + OutLinks.Length];
                InLinks.CopyTo(Resp, 0);
                OutLinks.CopyTo(Resp, InLinks.Length);
                return Resp;
            }
        }

        /// <summary>
        /// Report our collection of links by their attribute pointer name
        /// </summary>
        public Dictionary<CIM_Element, String> LinksByAttributeName
        {
            get
            {
                Dictionary<CIM_Element, String> OutDic = new Dictionary<CIM_Element, string>();
                foreach (KeyValuePair<CIM_Element, String> kvp in InLinksByAttributeName)
                    OutDic.Add(kvp.Key, kvp.Value);
                foreach (KeyValuePair<CIM_Element, String> kvp in OutLinksByAttributeName)
                    OutDic.Add(kvp.Key, kvp.Value);
                return OutDic;
            }
        }


        /// <summary>Elements in either direction</summary>
        public virtual CIM_Element[] Links_Elements
        {
            get
            {
                List<CIM_Element> OutElems = new List<CIM_Element>();
                foreach (CIM_Link Link in InLinks)
                    OutElems.Add(Link.Element);
                foreach (CIM_Link Link in OutLinks)
                    OutElems.Add(Link.Element);
                return OutElems.ToArray();
            }
        }
        #endregion

        #region Initialization
        /// <summary>
        /// Initialize a new CIM element
        /// </summary>
        /// <param name="dRd">The database reader</param>
        /// <param name="CIM">The CIM model</param>
        public CIM_Element(DbDataReader dRd, MM_Database_Model CIM)
            : base(dRd, null, CIM)
        {
            this.CIM = CIM;
        }

        /// <summary>
        /// Private initiator for a CIM element
        /// </summary>
        /// <param name="CIM"></param>
        internal CIM_Element(MM_Database_Model CIM)
            : base(null, CIM)
        {
            this.CIM = CIM;
        }
        #endregion

        #region Data retrieval
        /// <summary>
        /// Return a name that incorporates line information
        /// </summary>
        public string LineName
        {
            get
            {
                if (TypeName == "cim:ACLineSegment" || TypeName == "cim:SeriesCompensator")
                {
                    CIM_Element ParentLine = ParseSingle("cim:Line", null);
                    return MM_Type_Converter.TitleCase(ParentLine.Name + (ParentLine.InLinks.Length == 1 ? "" : Name));
                }
                else
                    return Name;
            }
        }



        /// <summary>
        /// Return a parsed value across elements 
        /// </summary>
        /// <param name="ParseString"></param>
        /// <returns></returns>
        public String this[String ParseString]
        {
            get
            {
                int LastPos = ParseString.LastIndexOfAny("<>=".ToCharArray());
                String TargetAttribute = ParseString.Substring(LastPos + 1);
                CIM_Element TargetElement = this;
                if (LastPos != -1)
                {
                    CIM_Element[] ParsedResult = this.Parse(ParseString.Substring(0, LastPos), null);
                    if (ParsedResult.Length == 0)
                        return null;
                    else
                        TargetElement = ParsedResult[0];
                }

                PropertyInfo pI = this.GetType().GetProperty(TargetAttribute);
                if (pI != null)
                    try
                    {
                        Object OutVal = pI.GetValue(TargetElement, null);
                        if (OutVal is CIM_Element)
                            return (OutVal as CIM_Element).Name;
                        else if (OutVal is CIM_Element[])
                        {
                            StringBuilder sB = new StringBuilder();
                            foreach (CIM_Element Elem in (CIM_Element[])OutVal)
                                sB.Append((sB.Length == 0 ? "" : ",") + Elem.LineName);
                            return sB.ToString();
                        }
                        else
                            return OutVal.ToString();
                    }
                    catch (Exception ex)
                    {
                        while (ex.InnerException != null)
                            ex = ex.InnerException;
                        return "ERROR: " + ex.Message;
                    }
                else if (TargetAttribute.Equals("Name", StringComparison.CurrentCultureIgnoreCase))
                    return TargetElement.Name;
                else if (TargetAttribute.Equals("rdfID", StringComparison.CurrentCultureIgnoreCase))
                    return TargetElement.rdfID.ToString();
                else if (TargetAttribute.Equals("Type", StringComparison.CurrentCultureIgnoreCase))
                    return TargetElement.TypeName;
                else
                    foreach (KeyValuePair<String, String> kvp in TargetElement.Attributes)
                        if (kvp.Key.EndsWith(TargetAttribute, StringComparison.CurrentCultureIgnoreCase))
                            return kvp.Value;

                return null;
            }
        }

        /// <summary>
        /// Parse through CIM elements according to the specified path
        /// </summary>
        /// <param name="ParseString">The path to be parsed</param>
        /// <param name="Repository">The repository that may contain additionally needed elements</param>        
        /// <returns></returns>
        public CIM_Element[] Parse(string ParseString, MM_Repository Repository)
        {
            return Parse(ParseString, Repository, false);
        }

        /// <summary>
        /// Parse through CIM elements according to the specified path
        /// </summary>
        /// <param name="ParseString">The path to be parsed</param>
        /// <param name="Repository">The repository that may contain additionally needed elements</param>
        /// <param name="ReturnFull">Whether to return the full collection of elements on the path</param>
        /// <returns></returns>
        public CIM_Element[] Parse(string ParseString, MM_Repository Repository, bool ReturnFull)
        {
            //First, look for our parse string within this component.
            PropertyInfo pI = this.GetType().GetProperty(ParseString);
            if (pI != null)
                if (pI.PropertyType == typeof(CIM_Element))
                    return new CIM_Element[] { pI.GetValue(this, null) as CIM_Element };
                else if (pI.PropertyType == typeof(CIM_Element[]))
                    return pI.GetValue(this, null) as CIM_Element[];
                else if (pI.PropertyType == typeof(List<CIM_Element>))
                    return (pI.GetValue(this, null) as List<CIM_Element>).ToArray();

            List<CIM_Element> OutElements = new List<CIM_Element>();
            List<CIM_Element> FullElements = new List<CIM_Element>();
            RecursiveParse(OutElements, FullElements, ParseString, 0, Repository, new CIM_Element[0], new CIM_Element[0]);
            if (ReturnFull)
                return FullElements.ToArray();
            else
                return OutElements.ToArray();
        }


        /// <summary>
        /// Parse through a string, and return the first response if any
        /// </summary>
        /// <param name="ParseString"></param>
        /// <param name="Repository">The data repository</param>
        /// <returns></returns>
        public CIM_Element ParseSingle(String ParseString, MM_Repository Repository)
        {
            CIM_Element[] InResults = Parse(ParseString, Repository);
            if (InResults.Length > 0)
                return InResults[0];
            else
                return null;
        }


        /// <summary>
        /// Recursively go through all elements, parsing out the touched ones
        /// </summary>
        /// <param name="OutElements"></param>
        /// <param name="FullElements"></param>
        /// <param name="CurrentPath"></param>
        /// <param name="LineToParse"></param>
        /// <param name="StartPosition"></param>
        /// <param name="Repository">The data repository</param>
        /// <param name="ParsedElements"></param>
        private void RecursiveParse(List<CIM_Element> OutElements, List<CIM_Element> FullElements, String LineToParse, int StartPosition, MM_Repository Repository, CIM_Element[] ParsedElements, CIM_Element[] CurrentPath)
        {
            //If we're at the end of our lines, return;
            int NextPos = LineToParse.IndexOfAny("<=>".ToCharArray(), StartPosition + 1);

            //First, update our outgoing list with the current element
            CIM_Element[] OutElems = new CIM_Element[ParsedElements.Length + 1];
            ParsedElements.CopyTo(OutElems, 0);
            OutElems[OutElems.Length - 1] = this;

            //Now determine the list of links we should be following
            CIM_Link[] LinkList;
            if (LineToParse[StartPosition] == '>')
                LinkList = OutLinks;
            else if (LineToParse[StartPosition] == '<')
                LinkList = InLinks;
            else
                LinkList = Links;

            //Include our current element in our path
            List<CIM_Element> UpdatedFullElements = new List<CIM_Element>(FullElements);
            FullElements.Add(this);

            //Go through those links to find our member
            int StartOffset = 1;// "<=>".ToCharArray().Contains(LineToParse[0]) ? 1 : 0;
            String TargetType = (NextPos == -1 ? LineToParse.Substring(StartPosition + StartOffset) : LineToParse.Substring(StartPosition + StartOffset, NextPos - StartPosition - StartOffset));

            //First, try and see if we have that property
            PropertyInfo pI = this.GetType().GetProperty(TargetType, typeof(CIM_Element));
            if (pI != null)
                if (NextPos == -1)
                {
                    OutElements.Add(pI.GetValue(this, null) as CIM_Element);
                    foreach (CIM_Element Elem in UpdatedFullElements)
                        if (!FullElements.Contains(Elem))
                            FullElements.Add(Elem);
                }
                else
                    (pI.GetValue(this, null) as CIM_Element).RecursiveParse(OutElements, FullElements, LineToParse, NextPos, Repository, OutElems, UpdatedFullElements.ToArray());
            else if (LinkList != null)
                foreach (CIM_Link Link in LinkList)
                    if (TargetType == "*" || Link.TypeName.EndsWith(TargetType, StringComparison.CurrentCultureIgnoreCase) || (TargetType.EndsWith("*") && Link.TypeName.Substring(Link.TypeName.IndexOf(':') + 1).StartsWith(TargetType.Replace("*", ""), StringComparison.CurrentCultureIgnoreCase)))
                        if (NextPos == -1)
                        {
                            OutElements.Add(CIM.FromID(Link.TargetID, Repository));
                            foreach (CIM_Element Elem in UpdatedFullElements)
                                if (!FullElements.Contains(Elem))
                                    FullElements.Add(Elem);
                        }
                        else
                            CIM.FromID(Link.TargetID, Repository).RecursiveParse(OutElements, FullElements, LineToParse, NextPos, Repository, OutElems, UpdatedFullElements.ToArray());
        }

     
        /// <summary>
        /// Convert XML to attributes
        /// </summary>
        /// <param name="InnerXml"></param>
        /// <param name="TypeName"></param>
        /// <returns></returns>
        public static Dictionary<String, String> ConvertXmlToAttributes(String InnerXml, String TypeName)
        {
            Dictionary<String, String> OutDic = new Dictionary<string, string>();
            String LastName = "";
            XmlNodeType LastType = XmlNodeType.EndElement;
            using (StringReader sRd = new StringReader(InnerXml))
            using (XmlTextReader xRd = new XmlTextReader(sRd))
            {
                xRd.Namespaces = false;
                xRd.WhitespaceHandling = WhitespaceHandling.None;
                while (xRd.Read())
                {
                    if (String.IsNullOrEmpty(LastName) && xRd.NodeType == XmlNodeType.Element)
                        AddAttribute(OutDic, "Type", xRd.Name);
                    if (xRd.NodeType == XmlNodeType.Element)
                        LastName = xRd.Name;
                    else if (xRd.NodeType == XmlNodeType.Text)
                        AddAttribute(OutDic, LastName, xRd.Value);
                    else if (xRd.NodeType == XmlNodeType.EndElement && LastType == XmlNodeType.Element)
                        AddAttribute(OutDic, LastName, "");
                    for (int a = 0; a < xRd.AttributeCount; a++)
                    {
                        xRd.MoveToAttribute(a);
                        if (xRd.Name == "rdf:resource")
                            AddAttribute(OutDic, LastName, xRd.Value);
                    }
                }
                LastType = xRd.NodeType;
            }
            return OutDic;
        }

        /// <summary>
        /// Add an attribute to our dictionary, and update to support any collisions
        /// </summary>
        /// <param name="AttributeDictionary"></param>
        /// <param name="Name"></param>
        /// <param name="Value"></param>
        private static void AddAttribute(Dictionary<String, String> AttributeDictionary, String Name, String Value)
        {
            if (AttributeDictionary.ContainsKey(Name))
            {
                int a = 1;
                while (AttributeDictionary.ContainsKey(Name + " (" + a + ")"))
                    a++;
                AttributeDictionary.Add(Name + " (" + a + ")", Value);
            }
            else
                AttributeDictionary.Add(Name, Value);
        }

        /// <summary>
        /// Retrieve the attributes for the element
        /// </summary>
        public virtual Dictionary<String, String> Attributes
        {
            get
            {
                Dictionary<String, String> OutDic;
                if (CIM == null || String.IsNullOrWhiteSpace(this.XML))
                    return new Dictionary<string, string>();
                //First, try and retrieve our element from the cache. 
#if !NoCacheing
                if (!CIM.ElementAttributes.TryGetValue(this, out OutDic))
                {
                    OutDic = ConvertXmlToAttributes(this.XML, this.TypeName);

                    //Now, add our element to our dictionary.
                    CIM.ElementAttributes.Add(this, OutDic);
                    if (CIM.ElementQueue.Count == MM_Database_Model.ElementQueueCapacity)
                        CIM.ElementAttributes.Remove(CIM.ElementQueue.Dequeue());
                    CIM.ElementQueue.Enqueue(this);
                }
#else
                    OutDic = ConvertXmlToAttributes(this.XML, this.TypeName);
#endif               
                return OutDic;
            }
        }

        /// <summary>
        /// Return the list of attributes in key/value pair format
        /// </summary>
        public virtual MM_CIM_SortedCollection AttributeList
        {
            get
            {
                MM_CIM_SortedCollection Attributes = new MM_CIM_SortedCollection();
                String LastName = "";
                XmlNodeType LastType = XmlNodeType.EndElement;
                String Xml = this.XML;
                if (String.IsNullOrEmpty(Xml))
                    return Attributes;
                using (StringReader sRd = new StringReader(Xml))
                using (XmlTextReader xRd = new XmlTextReader(sRd))
                {
                    xRd.Namespaces = false;
                    xRd.WhitespaceHandling = WhitespaceHandling.None;
                    while (xRd.Read())
                    {
                        if (xRd.NodeType == XmlNodeType.Element)
                            LastName = xRd.Name;
                        else if (xRd.NodeType == XmlNodeType.Text)
                            Attributes.Add(LastName, xRd.Value);
                        else if (xRd.NodeType == XmlNodeType.EndElement && LastType == XmlNodeType.Element)
                            Attributes.Add(LastName, "");
                        for (int a = 0; a < xRd.AttributeCount; a++)
                        {
                            xRd.MoveToAttribute(a);
                            if (xRd.Name == "rdf:resource")
                                Attributes.Add(LastName, xRd.Value);
                        }
                        LastType = xRd.NodeType;
                    }
                }
                return Attributes;
            }
        }
        

        /// <summary>
        /// Retrieve the XML for this element
        /// </summary>
        public virtual String XML
        {
            get
            {
                if (CIM.ModelChanges.Count == 0)
                    if (Start == -1 || Length == 0)
                        return "";
                    else
                        using (FileStream fS = new FileStream(CIM.CIMFileName, FileMode.Open, FileAccess.Read, FileShare.Read, this.Length))
                        {
                            fS.Seek(this.Start, SeekOrigin.Begin);
                            Byte[] inB = new byte[this.Length];
                            fS.Read(inB, 0, inB.Length);
                            fS.Close();
                            return Encoding.UTF8.GetString(inB);
                        }
                else
                {
                    String InnerXml;
                    using (FileStream fS = new FileStream(CIM.CIMFileName, FileMode.Open, FileAccess.Read, FileShare.Read, this.Length))
                    {
                        fS.Seek(this.Start, SeekOrigin.Begin);
                        Byte[] inB = new byte[this.Length];
                        fS.Read(inB, 0, inB.Length);
                        fS.Close();
                        InnerXml =  Encoding.UTF8.GetString(inB);
                    }

                    StringBuilder OutXml = new StringBuilder();
                    MM_CIM_SortedCollection Attributes = new MM_CIM_SortedCollection();
                    foreach (KeyValuePair<String, String> kvp in this.Attributes)
                        Attributes.Add(kvp.Key, kvp.Value);



                    //Attempt to locate our element difference
                    CIM_Element_Difference eDiff = null;
                    CIM.ModelChanges.TryGetValue(this.rdfID, out eDiff);



                    //Start by writing the header
                    OutXml.AppendLine("<" + this.TypeName + " rdf:ID=\"" + this.rdfID.ToString() + "\">");

                    //Remove all attributes as needed
                    if (eDiff != null && eDiff.RemovedAttributes != null)
                        foreach (KeyValuePair<String, String> kvp in eDiff.RemovedAttributes)
                            Attributes.Remove(kvp.Key, kvp.Value);

                    //Add in our elements as needed
                    if (eDiff != null && eDiff.AddedAttributes != null)
                        Attributes.AddRange(eDiff.AddedAttributes);

                    //Now, write out each element
                    if (Attributes != null)
                        foreach (KeyValuePair<String, String> kvp in Attributes)
                            if (kvp.Value.StartsWith("#"))
                                OutXml.AppendLine("\t\t<" + kvp.Key + " rdf:resource=\"" + kvp.Value + "\"/>");
                            else
                                OutXml.AppendLine("\t\t<" + kvp.Key + ">" + kvp.Value + "</" + kvp.Key + ">");

                    //Now, write out the closing of our element
                    OutXml.AppendLine("\t</" + this.TypeName + ">");

                    return OutXml.ToString();
                }
            }
        }


        #endregion

        #region Element-specific parsing
        /// <summary>
        /// Return the name of the element
        /// </summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue)]
        public virtual String Name
        {
            get
            {
                if (_Name == null)
                    Attributes.TryGetValue("cim:IdentifiedObject.name", out _Name);
                return _Name;
            }
            set { _Name = value; }
        }

        /// <summary>
        /// Return the description of the element
        /// </summary>        
        public virtual String LongName
        {
            get
            {
                String _LongName;
                if (Attributes.TryGetValue("cim:IdentifiedObject.description", out _LongName) && !String.IsNullOrEmpty(_LongName))
                    return _LongName;
                else if (TypeName == "cim:ACLineSegment" || TypeName == "cim:SeriesCompensator")
                {
                    CIM_Element ParentLine = ParseSingle("cim:Line", null);
                    return MM_Type_Converter.TitleCase(ParentLine.Name + (ParentLine.InLinks.Length == 1 ? "" : Name) + "\n(" + ACSegmentStations[0].LongName + " to " + ACSegmentStations[1].LongName + ")");
                }
                else
                    return Name;
            }
        }

        /// <summary>
        /// Report the bus numbers associated with an element
        /// </summary>
        public int[] BusNumbers
        {
            get
            {
                int FoundBus;
                List<int> Buses = new List<int>();
                if (TypeName == "cim:Substation")
                {
                    foreach (CIM_Element CNG in Parse("<cim:VoltageLevel<etx:ConnectivityNodeGroup", CIM))
                        if (Int32.TryParse(CNG["etx:ConnectivityNodeGroup.PSSEBusNumber"], out FoundBus) && !Buses.Contains(FoundBus))
                            Buses.Add(FoundBus);
                }
                else
                {
                }
                return Buses.ToArray();
            }
        }

        /// <summary>
        /// Return the connectivity nodes around the current element.
        /// </summary>
        public CIM_Element[] ACSegmentNodes
        {
            get
            {
                if (this.TypeName == "cim:ACLineSegment" || this.TypeName == "cim:SeriesCompensator")
                {
                    List<CIM_Element> Out = new List<CIM_Element>(5);
                    foreach (CIM_Link Term in this.InLinks)
                        if (Term.TypeName == "cim:Terminal")
                            foreach (CIM_Link Node in Term.Element.OutLinks)
                                if (Node.TypeName == "cim:ConnectivityNode")
                                    Out.Add(Node.Element);
                    return Out.ToArray();
                }


                else if (this.TypeName == "cim:Line")
                {
                    foreach (CIM_Link LineSeg in this.InLinks)
                        if (LineSeg.TypeName == "cim:ACLineSegment" || LineSeg.TypeName == "cim:SeriesCompensator")
                            return LineSeg.Element.ACSegmentNodes;
                    throw new InvalidOperationException("Line nodes can only be retrieved for Lines, ACLineSegments and SeriesCompensators!");
                }
                else
                    throw new InvalidOperationException("Line nodes can only be retrieved for Lines, ACLineSegments and SeriesCompensators!");

            }
        }

        /// <summary>
        /// Return the substations around the current element. Particularly useful
        /// for an AC Line Segment, as it will return the stations connected to the line.
        /// </summary>
        public CIM_Element[] ACSegmentStations
        {
            get
            {
                if (this.TypeName == "cim:ACLineSegment" || this.TypeName == "cim:SeriesCompensator")
                {
                    List<CIM_Element> Out = new List<CIM_Element>(5);
                    foreach (CIM_Link Term in this.InLinks)
                        if (Term.TypeName == "cim:Terminal")
                            foreach (CIM_Link Node in Term.Element.OutLinks)
                                if (Node.TypeName == "cim:ConnectivityNode")
                                    Out.Add(Node.Element.Substation);
                    return Out.ToArray();
                }


                else if (this.TypeName == "cim:Line")
                {
                    foreach (CIM_Link LineSeg in this.InLinks)
                        if (LineSeg.TypeName == "cim:ACLineSegment" || LineSeg.TypeName == "cim:SeriesCompensator")
                            return LineSeg.Element.ACSegmentStations;
                    throw new InvalidOperationException("Line stations can only be retrieved for Lines, ACLineSegments and SeriesCompensators!");
                }
                else
                    throw new InvalidOperationException("Line stations can only be retrieved for Lines, ACLineSegments and SeriesCompensators!");

            }
        }

        /// <summary>
        /// Report the list of lines attached to this substation
        /// </summary>
        public CIM_Element[] ConnectedLines
        {
            get
            {
                List<CIM_Element> OutStations = new List<CIM_Element>();
                if (TypeName == "cim:Substation")
                    foreach (CIM_Element Line in Parse("cim:VoltageLevel<cim:ConnectivityNode<cim:Terminal>cim:ACLineSegment", null))
                        OutStations.Add(Line);
                else if (TypeName == "cim:Contingency" || TypeName == "cim:Company")
                {
                    foreach (CIM_Element Elem in this.Elements)
                        if (Elem.TypeName == "cim:ACLineSegment")
                            OutStations.Add(Elem);
                }
                else if (TypeName == "cim:SubGeographicalRegion" || TypeName == "etx:SettlementLoadZone")
                    OutStations.AddRange(Parse("<cim:Line<cim:ACLineSegment", null));
                else
                    throw new InvalidOperationException("Unable to determine lines connected to " + TypeName);
                return OutStations.ToArray();

            }
        }


        /// <summary>
        /// Report the list of substations attached to this one
        /// </summary>
        public CIM_Element[] ConnectedStations
        {
            get
            {
                List<CIM_Element> OutStations = new List<CIM_Element>();
                if (TypeName == "cim:Substation")
                {
                    foreach (CIM_Element Line in Parse("cim:VoltageLevel<cim:ConnectivityNode<cim:Terminal>cim:ACLineSegment", null))
                        foreach (CIM_Element LineStation in Line.ACSegmentStations)
                            if (!LineStation.Equals(this) && !OutStations.Contains(LineStation))
                                OutStations.Add(LineStation);
                }
                else if (TypeName == "cim:ACLineSegment")
                    return ACSegmentStations;
                else if (TypeName == "cim:Contingency" || TypeName == "cim:Company")
                {
                    foreach (CIM_Element Elem in this.Elements)
                        if (Elem.Substation != null && !OutStations.Contains(Elem.Substation))
                            OutStations.Add(Elem.Substation);
                        else if (Elem.TypeName == "cim:ACLineSegment" || Elem.TypeName == "cim:SeriesCompensator")
                            foreach (CIM_Element ElemSt in Elem.ACSegmentStations)
                                if (!OutStations.Contains(ElemSt))
                                    OutStations.Add(ElemSt);
                }
                else if (TypeName == "cim:SubGeographicalRegion" || TypeName == "etx:SettlementLoadZone")
                    OutStations.AddRange(Parse("<cim:Substation", null));
                else
                    throw new InvalidOperationException("Unable to determine substations connected to " + TypeName);
                return OutStations.ToArray();

            }
        }

        /// <summary>
        /// Report a list of substation rdf:IDs        
        /// </summary>
        public String ConnectedStationsRdfIDs
        {
            get
            {
                StringBuilder sB = new StringBuilder();
                foreach (CIM_Element Sub in ConnectedStations)
                    sB.Append((sB.Length==0?"":",")+Sub.rdfID.ToString());
                return sB.ToString();
            }        
        }

        /// <summary>
        /// Report the list of lines attached to this one
        /// </summary>
        public String ConnectedLineLongNames
        {
            get
            {
                List<String> OutLines = new List<string>();
                if (TypeName == "cim:Substation")
                    foreach (CIM_Element Line in ConnectedLines)
                        OutLines.Add(Line.LineName);
                return String.Join(",", OutLines.ToArray());
            }
            
        }

        /// <summary>
        /// Report the list of substations attached to this one
        /// </summary>
        public String ConnectedStationLongNames
        {
            get
            {
                List<String> OutStations = new List<String>();
                if (this is CIM_Trace)
                {
                    foreach (CIM_Element Substation in (this as CIM_Trace).Substations)
                        OutStations.Add(MM_Type_Converter.TitleCase(Substation.LongName));
                }
                else if (TypeName == "cim:Substation")
                {
                    foreach (CIM_Element Line in Parse("cim:VoltageLevel<cim:ConnectivityNode<cim:Terminal>cim:ACLineSegment", null))
                        foreach (CIM_Element LineStation in Line.ACSegmentStations)
                            if (!LineStation.Equals(this) && !OutStations.Contains(LineStation.LongName))
                                OutStations.Add(MM_Type_Converter.TitleCase(LineStation.LongName));
                }
                else if (TypeName == "cim:ACLineSegment")
                    foreach (CIM_Element Sub in ConnectedStations)
                        OutStations.Add(MM_Type_Converter.TitleCase(Sub.LongName));
                else if (TypeName == "cim:Contingency" || TypeName == "cim:Company")
                {
                    foreach (CIM_Element Elem in this.Elements)
                        if (Elem.Substation != null && !OutStations.Contains(Elem.Substation.LongName))
                            OutStations.Add(MM_Type_Converter.TitleCase(Elem.Substation.LongName));
                        else if (Elem.TypeName == "cim:ACLineSegment" || Elem.TypeName == "cim:SeriesCompensator")
                            foreach (CIM_Element ElemSt in Elem.ACSegmentStations)
                                if (!OutStations.Contains(MM_Type_Converter.TitleCase(ElemSt.LongName)))
                                    OutStations.Add(MM_Type_Converter.TitleCase(ElemSt.LongName));
                }
                else if (TypeName == "cim:SubGeographicalRegion" || TypeName == "etx:SettlementLoadZone")
                    foreach (CIM_Element Sub in Parse("<cim:Substation", null))
                        OutStations.Add(MM_Type_Converter.TitleCase(Sub.LongName));
                else
                    throw new InvalidOperationException("Unable to determine substations connected to " + TypeName);

                OutStations.Sort();
                return String.Join(",", OutStations.ToArray());                
            }
        }

        /// <summary>
        /// Determine, if possible, the substation associated with the current element
        /// </summary>
        public virtual CIM_Element Substation
        {
            get
            {
                if (this.TypeName == "cim:Substation")
                    return this;
                else if (this.TypeName == "cim:Analog")
                    return this.ParseSingle("cim:Terminal>cim:ConnectivityNode>cim:VoltageLevel>cim:Substation", null);
                else if (this.TypeName == "etx:EPSMeter")
                    return ParseSingle("<etx:PricingVector>cim:Terminal>cim:ConnectivityNode>cim:VoltageLevel>cim:Substation", null);
                else if (this.TypeName == "etx:PricingVector")
                    return ParseSingle(">cim:Terminal>cim:ConnectivityNode>cim:VoltageLevel>cim:Substation", null);
                else if (this.TypeName == "cim:TransformerWinding")
                    return ParseSingle(">cim:PowerTransformer>cim:Substation", null);
                else if (this.TypeName == "etx:DCTie")
                    return ParseSingle(">Unit<cim:SynchronousMachine>cim:VoltageLevel>cim:Substation", null);
                else if (this.TypeName == "etx:ElectricalBus" || this.TypeName == "cim:Terminal")
                    return ParseSingle(">cim:ConnectivityNode>cim:VoltageLevel>cim:Substation", null);
                else if (this.TypeName == "etx:ResourceNode")
                    return ParseSingle(">etx:ElectricalBus>cim:ConnectivityNode>cim:VoltageLevel>cim:Substation", null);
                else if (this.TypeName.EndsWith("GeneratingUnit", StringComparison.CurrentCultureIgnoreCase))
                    return ParseSingle("<cim:SynchronousMachine>cim:VoltageLevel>cim:Substation", null);
                else if (this.TypeName == "cim:SeriesCompensator")
                    return this.Nodes[0].Substation;
                else if (this.TypeName == "cim:CombinedCyclePlant")
                    return ParseSingle("<Unit<cim:SynchronousMachine>cim:VoltageLevel>cim:Substation", null);
                else
                    foreach (CIM_Link Link in OutLinks)
                        if (Link.TypeName == "cim:Bay")
                            return CIM.FromID(Link.TargetID, null).ParseSingle(">cim:VoltageLevel>cim:Substation", null);
                        else if (Link.TypeName == "cim:Substation")
                            return CIM.FromID(Link.TargetID, null);
                        else if (Link.TypeName == "cim:VoltageLevel")
                            return CIM.FromID(Link.TargetID, null).ParseSingle(">cim:Substation", null);
                return null;
            }
        }

        /// <summary>
        /// Find and return our appropriate operator
        /// </summary>
        /// <returns></returns>
        public CIM_Element FindOperator()
        {
            if (Operator != null)
                return Operator;
            else if (Substation != null)
                return Substation.Operator;
            else if (ParseSingle("cim:Line", CIM) != null)
                return ParseSingle("cim:Line", CIM).Operator;
            else
                return null;
        }
        /// <summary>
        /// Find and return our appropriate Owner
        /// </summary>
        /// <returns></returns>
        public CIM_Element FindOwner()
        {
            if (Owner != null)
                return Owner;
            else if (Substation != null)
                return Substation.Owner;
            else if (ParseSingle("cim:Line", CIM) != null)
                return ParseSingle("cim:Line", CIM).Owner;
            else
                return null;
        }

        /// <summary>
        /// Return the operator for the specified element
        /// </summary>
        public CIM_Element Operator
        {
            get
            {
                //if (this.TypeName.EndsWith("Unit"))
                //    return ParseSingle(">cim:SynchronousMachine>cim:VoltageLevel>etx:Operatorship>cim:Company");
                if (this.TypeName.EndsWith("GeneratingUnit", StringComparison.CurrentCultureIgnoreCase))
                    return ParseSingle("<etx:OwnerShareLimits>etx:Operatorship>cim:Company", null);
                else if (this.TypeName == "cim:SynchronousMachine")
                    return ParseSingle(">Unit<etx:OwnerShareLimits>etx:Operatorship>cim:Company", null);
                else if (this.TypeName == "etx:EPSMeter")
                    return ParseSingle(">etx:PricingVector>etx:ElectricalBus>cim:ConnectivityNode>cim:VoltageLevel>etx:Operatorship>cim:Company", null);
                else if (this.TypeName == "etx:PricingVector")
                    return ParseSingle(">etx:ElectricalBus>cim:ConnectivityNode>cim:VoltageLevel>etx:Operatorship>cim:Company", null);
                CIM_Element DirectOperatorship = ParseSingle(">etx:Operatorship>cim:Company", null);
                if (DirectOperatorship != null)
                    return DirectOperatorship;
                DirectOperatorship = ParseSingle(">cim:VoltageLevel>etx:Operatorship>cim:Company", null);
                if (DirectOperatorship != null)
                    return DirectOperatorship;
                DirectOperatorship = ParseSingle(">cim:Bay>etx:Operatorship>cim:Company", null);
                return DirectOperatorship;

                

            }
        }

        /// <summary>
        /// Determine the resource owners of the item in question, and their percentages
        /// </summary>
        public Dictionary<CIM_Element, float> ResourceOwners
        {
            get
            {
                Dictionary<CIM_Element, float> OutRes = new Dictionary<CIM_Element, float>();
                if (this.TypeName == "cim:SynchronousMachine")
                    foreach (CIM_Element OwnerShare in this.Parse(">GeneratingUnit<etx:OwnerShareLimits>etx:Ownership", null))
                    {
                        CIM_Element Company = OwnerShare.ParseSingle("cim:Company", null);
                        if (!OutRes.ContainsKey(Company))
                            OutRes.Add(Company, float.Parse(OwnerShare["etx:Operatorship.ownPercent"] ?? "100"));
                    }


                else if (this.TypeName.EndsWith("GeneratingUnit", StringComparison.CurrentCultureIgnoreCase))
                    foreach (CIM_Element OwnerShare in this.Parse("<etx:OwnerShareLimits>etx:Ownership", null))
                    {
                        CIM_Element Company = OwnerShare.ParseSingle("cim:Company", null);
                        if (!OutRes.ContainsKey(Company))
                            OutRes.Add(Company, float.Parse(OwnerShare["etx:Operatorship.ownPercent"] ?? "100"));
                    }
                else if (this.TypeName == "etx:EPSMeter")
                    foreach (CIM_Element OwnerShare in Parse(">etx:PricingVector>etx:ElectricalBus>cim:ConnectivityNode>cim:VoltageLevel>etx:Ownership", null))
                    {
                        CIM_Element Company = OwnerShare.ParseSingle("cim:Company", null);
                        if (!OutRes.ContainsKey(Company))
                            OutRes.Add(Company, float.Parse(OwnerShare["etx:Operatorship.ownPercent"] ?? "100"));
                    }
                else if (this.TypeName == "etx:PricingVector")
                    foreach (CIM_Element OwnerShare in Parse(">etx:ElectricalBus>cim:ConnectivityNode>cim:VoltageLevel>etx:Ownership", null))
                    {
                        CIM_Element Company = OwnerShare.ParseSingle("cim:Company", null);
                        if (!OutRes.ContainsKey(Company))
                            OutRes.Add(Company, float.Parse(OwnerShare["etx:Operatorship.ownPercent"] ?? "100"));
                    }
                else if (ParseSingle(">etx:Ownership", null) != null)
                    foreach (CIM_Element OwnerShare in Parse(">etx:Ownership", null))
                    {
                        CIM_Element Company = OwnerShare.ParseSingle("cim:Company", null);
                        if (!OutRes.ContainsKey(Company))
                            OutRes.Add(Company, float.Parse(OwnerShare["etx:Operatorship.ownPercent"] ?? "100"));
                    }
                else
                    foreach (CIM_Element OwnerShare in Parse(">cim:VoltageLevel>etx:Ownership", null))
                    {
                        CIM_Element Company = OwnerShare.ParseSingle("cim:Company", null);
                        if (!OutRes.ContainsKey(Company))
                            OutRes.Add(Company, float.Parse(OwnerShare["etx:Operatorship.ownPercent"] ?? "100"));
                    }
                return OutRes;
            }
        }

        /// <summary>
        /// Determine the resource operators of the item in question, and their percentages
        /// </summary>
        public Dictionary<CIM_Element, float> ResourceOperators
        {
            get
            {
                Dictionary<CIM_Element, float> OutRes = new Dictionary<CIM_Element, float>();
                if (this.TypeName == "cim:SynchronousMachine")
                    foreach (CIM_Element OperatorShare in this.Parse(">GeneratingUnit<etx:OwnerShareLimits>etx:Operatorship", null))
                    {
                        CIM_Element Company = OperatorShare.ParseSingle("cim:Company", null);
                        if (!OutRes.ContainsKey(Company))
                            OutRes.Add(Company, float.Parse(OperatorShare["etx:Operatorship.operatePercent"] ?? "100"));
                    }
                else if (this.TypeName.EndsWith("GeneratingUnit", StringComparison.CurrentCultureIgnoreCase))
                    foreach (CIM_Element OperatorShare in this.Parse("<etx:OwnerShareLimits>etx:Operatorship", null))
                    {
                        CIM_Element Company = OperatorShare.ParseSingle("cim:Company", null);
                        if (!OutRes.ContainsKey(Company))
                            OutRes.Add(Company, float.Parse(OperatorShare["etx:Operatorship.operatePercent"] ?? "100"));
                    }
                else if (this.TypeName == "etx:EPSMeter")
                    foreach (CIM_Element OperatorShare in Parse(">etx:PricingVector>etx:ElectricalBus>cim:ConnectivityNode>cim:VoltageLevel>etx:Operatorship", null))
                    {
                        CIM_Element Company = OperatorShare.ParseSingle("cim:Company", null);
                        if (!OutRes.ContainsKey(Company))
                            OutRes.Add(Company, float.Parse(OperatorShare["etx:Operatorship.operatePercent"] ?? "100"));
                    }

                else if (this.TypeName == "etx:PricingVector")
                    foreach (CIM_Element OperatorShare in Parse(">etx:ElectricalBus>cim:ConnectivityNode>cim:VoltageLevel>etx:Operatorship", null))
                    {
                        CIM_Element Company = OperatorShare.ParseSingle("cim:Company", null);
                        if (!OutRes.ContainsKey(Company))
                            OutRes.Add(Company, float.Parse(OperatorShare["etx:Operatorship.operatePercent"] ?? "100"));
                    }
                else if (ParseSingle(">etx:Operatorship", null) != null)
                    foreach (CIM_Element OperatorShare in Parse(">etx:Operatorship", null))
                    {
                        CIM_Element Company = OperatorShare.ParseSingle("cim:Company", null);
                        if (!OutRes.ContainsKey(Company))
                            OutRes.Add(Company, float.Parse(OperatorShare["etx:Operatorship.operatePercent"] ?? "100"));
                    }
                else
                    foreach (CIM_Element OperatorShare in Parse(">cim:VoltageLevel>etx:Operatorship", null))
                    {
                        CIM_Element Company = OperatorShare.ParseSingle("cim:Company", null);
                        if (!OutRes.ContainsKey(Company))
                            OutRes.Add(Company, float.Parse(OperatorShare["etx:Operatorship.operatePercent"] ?? "100"));
                    }
                return OutRes;
            }
        }



        /// <summary>
        /// When possible, return the owner of the item in question
        /// </summary>
        /// <returns></returns>
        public CIM_Element Owner
        {
            get
            {
                if (this.TypeName.EndsWith("GeneratingUnit", StringComparison.CurrentCultureIgnoreCase))
                    return ParseSingle("<etx:OwnerShareLimits>etx:Ownership>cim:Company", null);
                else if (this.TypeName == "cim:SynchronousMachine")
                    return ParseSingle(">Unit<etx:OwnerShareLimits>etx:Ownership>cim:Company", null);
                else if (this.TypeName == "etx:EPSMeter")
                    return ParseSingle(">etx:PricingVector>etx:ElectricalBus>cim:ConnectivityNode>cim:VoltageLevel>etx:Ownership>cim:Company", null);
                else if (this.TypeName == "etx:PricingVector")
                    return ParseSingle(">etx:ElectricalBus>cim:ConnectivityNode>cim:VoltageLevel>etx:Ownership>cim:Company", null);
                CIM_Element DirectOwnership = ParseSingle(">etx:Ownership>cim:Company", null);
                if (DirectOwnership != null)
                    return DirectOwnership;                
                DirectOwnership = ParseSingle(">cim:VoltageLevel>etx:Ownership>cim:Company", null);
                if (DirectOwnership != null)
                    return DirectOwnership;
                DirectOwnership = ParseSingle(">cim:Bay>etx:Ownership>cim:Company", null);
                return DirectOwnership;
            }
        }

        /// <summary>
        /// Determine if an element is associated with one or more SPSs or RAPs
        /// </summary>
        public virtual CIM_Element[] SPSorRAPs
        {
            get
            {
                List<CIM_Element> SPSElements = new List<CIM_Element>();
                SPSElements.AddRange(this.Parse("<ProtAct*>PrivateProtectiveActions>StageTrigger>Stage>RemedialActionScheme", null));
                SPSElements.AddRange(this.Parse("<Terminal<Pin*>Gate>TriggerCondition>RemedialActionScheme", null));
                return SPSElements.ToArray();
            }
        }

        /// <summary>
        /// Determine the elements associated with the inputs for SPSs and RAPs
        /// </summary>
        public virtual CIM_Element[] SPSorRAPInputs
        {
            get
            {
                List<CIM_Element> SPSElements = new List<CIM_Element>();
                foreach (CIM_Element Elem in this.Parse("<TriggerCondition<Gate<Pin*>*", null))
                    if (Elem.TypeName == "cim:Terminal")
                        foreach (CIM_Link OutLink in Elem.OutLinks)
                            SPSElements.Add(OutLink.Element);
                    else if (Elem.TypeName != "etx:Gate")
                        SPSElements.Add(Elem);
                return SPSElements.ToArray();
            }
        }

        /// <summary>
        /// Report the list of breaker-to-breaker traces associated with the element
        /// </summary>
        public virtual CIM_Trace[] AssociatedTraces
        {
            get
            {
                List<CIM_Trace> Traces = new List<CIM_Trace>();
                foreach (CIM_Trace Trace in CIM.Traces.Values)
                    if (Array.IndexOf(Trace.rdfIDs, rdfID) != -1 && !Trace.Name.StartsWith("N_"))
                        Traces.Add(Trace);
                return Traces.ToArray();
            }
        }

        /// <summary>
        /// Determine the elements associated with the inputs for SPSs and RAPs
        /// </summary>
        public virtual CIM_Element[] SPSorRAPOutputs
        {
            get
            {
                List<CIM_Element> SPSElements = new List<CIM_Element>();
                foreach (CIM_Element Elem in this.Parse("<Stage<StageTrigger<PrivateProtectiveActions<ProtAct*>*",null))
                    if (!Elem.TypeName.StartsWith("etx:Private"))                       
                            SPSElements.Add(Elem);                    
                return SPSElements.ToArray();
            }
        }

        

        /// <summary>
        /// If the current element is a Connectivity node, go through its terminals
        /// to find the elements on the other side. 
        /// Also, if it's a power transformer, return the transformer windings on each side.
        /// Also, if it's a substation, return all elements within the substation.
        /// </summary>
        public virtual CIM_Element[] Elements
        {
            get
            {
                List<CIM_Element> NodeElements = new List<CIM_Element>();
                if (this.TypeName == "etx:PricingVector")
                    NodeElements.AddRange(Parse(">cim:Terminal>*", null));
                else if (this.TypeName == "etx:RemedialActionScheme")
                {
                    foreach (CIM_Element Elem in Parse("<Stage<StageTrigger<PrivateProtectiveActions<*>*", null))
                        if (!Elem.TypeName.StartsWith("etx:Private"))
                            NodeElements.Add(Elem);
                }
                else if (this.TypeName == "cim:PowerTransformer")
                    NodeElements.AddRange(Parse("<cim:TransformerWinding", null));
                else if (this.TypeName == "cim:BusbarSection")
                {
                    foreach (CIM_Element Node in Parse("<cim:Terminal>cim:ConnectivityNode", null))
                        foreach (CIM_Element NodeElem in Node.Elements)
                            if (!NodeElem.Equals(this))
                                NodeElements.Add(NodeElem);
                }
                else if (this.TypeName == "cim:Terminal")
                {
                    foreach (CIM_Link ElemLink in this.OutLinks)
                        if (ElemLink.TypeName != "cim:ConnectivityNode")
                            NodeElements.Add(ElemLink.Element);
                }
                else if (this.TypeName == "cim:ConnectivityNode")
                {
                    foreach (CIM_Link term in this.InLinks)
                        if (term.TypeName == "cim:Terminal")
                        {
                            foreach (CIM_Link otherElem in term.Element.InLinks)
                                if (otherElem.TypeName == "etx:PricingVector")
                                    NodeElements.Add(otherElem.Element);
                            foreach (CIM_Link otherElem in term.Element.OutLinks)
                                if ((otherElem.TargetID != this.ID) && (!NodeElements.Contains(otherElem.Element)))
                                    NodeElements.Add(otherElem.Element);
                        }
                    //For Joel Koepke: Added in Resource node from node parsing
                    //else if (term.TypeName == "etx:ElectricalBus")
                    //    foreach (CIM_Link ResourceNode in term.Element.InLinksWithProcessing)
                    //        if (ResourceNode.TypeName == "etx:ResourceNode")
                    //            NodeElements.Add(ResourceNode.Element);

                }
                else if (this.TypeName == "cim:Contingency")
                {
                    foreach (CIM_Link ContingencyElement in this.InLinks)
                        if (ContingencyElement.TypeName == "etx:ContingencyElement")
                            foreach (CIM_Link OtherSide in ContingencyElement.Element.OutLinks)
                                if (OtherSide.TypeName != "cim:Contingency")
                                    NodeElements.Add(OtherSide.Element);
                }              
                else if (this.TypeName == "cim:Bay")
                {
                    foreach (CIM_Link Elem in this.InLinks)
                        if (!NodeElements.Contains(Elem.Element))
                            NodeElements.Add(Elem.Element);
                }
                else if (this.TypeName == "cim:Substation")
                {

                    foreach (CIM_Link VoltageLevel in this.InLinks)
                        if (VoltageLevel.TypeName == "cim:PowerTransformer" && !NodeElements.Contains(VoltageLevel.Element))
                            NodeElements.Add(VoltageLevel.Element);
                        else if (VoltageLevel.TypeName == "cim:VoltageLevel")
                            foreach (CIM_Link Elem in VoltageLevel.Element.InLinks)
                                if (!NodeElements.Contains(Elem.Element))
                                {
                                    //If we have a connectivity node, make sure it's not included w/ a busbar, and also check 
                                    //to see whether it's attached to a line
                                    if (Elem.TypeName == "cim:ConnectivityNode")
                                    {
                                        foreach (CIM_Element Elem2 in Elem.Element.Elements)
                                            if (!NodeElements.Contains(Elem2))
                                                NodeElements.Add(Elem2);
                                        CIM_Element BusBar = Elem.Element.ParseSingle("<cim:Terminal>cim:BusbarSection", null);
                                        if (BusBar != null && !NodeElements.Contains(BusBar))
                                            NodeElements.Add(BusBar);
                                        NodeElements.Add(Elem.Element);
                                    }
                                    else
                                        NodeElements.Add(Elem.Element);
                                }
                }
                else if (this.TypeName == "cim:Company")
                {
                    foreach (CIM_Link InLink in this.InLinks)
                        if (InLink.TypeName == "etx:Operatorship")
                            foreach (CIM_Link InLinkElem in InLink.Element.InLinks)
                                if (InLinkElem.TypeName == "etx:OwnerShareLimits")
                                    NodeElements.AddRange(InLinkElem.Element.Parse("Unit<cim:SynchronousMachine", null));
                                else if (InLinkElem.TypeName == "cim:CombinedCyclePlant")
                                    NodeElements.Add(InLinkElem.Element);
                                else if (InLinkElem.TypeName.EndsWith("Unit"))
                                    NodeElements.Add(InLinkElem.Element.ParseSingle("cim:SynchronousMachine", null));
                                else if (InLinkElem.Element.VoltageLevel != null)
                                {
                                    NodeElements.Add(InLinkElem.Element);
                                    if (InLinkElem.TypeName == "cim:TransformerWinding")
                                        NodeElements.Add(InLinkElem.Element.ParseSingle("cim:PowerTransformer", null));
                                }
                                else if (InLinkElem.TypeName == "etx:ResourceController" || InLinkElem.TypeName == "cim:Analog" || InLinkElem.TypeName == "cim:Discrete")
                                { }
                                else if (InLinkElem.Element.TypeName == "cim:Substation")
                                {
                                    foreach (CIM_Element SubElem in InLinkElem.Element.Elements)
                                        if (SubElem.Operator == this || SubElem.Operator == null)
                                            NodeElements.Add(SubElem);
                                }
                                else
                                { }
                    //Now run through our elements, adding in those in the same voltage level as our equipment
                    int MaxNode = NodeElements.Count;
                    for (int a = 0; a < MaxNode; a++)
                    {
                        CIM_Element VoltageLevel = NodeElements[a].ParseSingle(">cim:VoltageLevel", null);
                        if (VoltageLevel != null)
                            foreach (CIM_Link Elem2 in VoltageLevel.InLinks)
                                NodeElements.Add(Elem2.Element);
                    }


                }
                else
                { }

                //Remove duplicates
                Dictionary<CIM_Element, bool> dic = new Dictionary<CIM_Element, bool>();
                foreach (CIM_Element Elem in NodeElements)
                    if (Elem == null)
                    { }
                    else if (!dic.ContainsKey(Elem))
                        dic.Add(Elem, true);

                return dic.Keys.ToArray();
            }
        }

        /// <summary>
        /// If the current element is an Element (not a connectivity node, but connected to terminals),
        /// go through its terminals to find elements on the other side.
        /// </summary>
        public CIM_Element[] Nodes
        {
            get
            {
                //Now go from terminal -> next
                List<CIM_Element> NodeElements = new List<CIM_Element>(8);

                if (this.TypeName == "cim:ConnectivityNode")
                { }
                else if (this.TypeName == "etx:PricingVector")
                    return Parse(">cim:Terminal>cim:ConnectivityNode", null);
                else if (this.TypeName == "cim:Line")
                {
                    foreach (CIM_Link LineSeg in this.Links)
                        if (LineSeg.TypeName == "cim:ACLineSegment" || LineSeg.TypeName == "cim:SeriesCompensator")
                            return LineSeg.Element.Nodes;
                }
                else if (this.TypeName == "cim:Substation")
                {
                    foreach (CIM_Link KVLevel in this.InLinks)
                        if (KVLevel.TypeName == "cim:VoltageLevel")
                            foreach (CIM_Link Elem in KVLevel.Element.InLinks)
                                if (Elem.TypeName == "cim:ConnectivityNode" && !NodeElements.Contains(Elem.Element))
                                    NodeElements.Add(Elem.Element);
                }
                else if (this.TypeName == "cim:PowerTransformer")
                {
                    foreach (CIM_Link TransformerWinding in this.Links)
                        if (TransformerWinding.TypeName == "cim:TransformerWinding")
                            foreach (CIM_Element Node in TransformerWinding.Element.Nodes)
                                if (!Node.Equals(this))
                                    NodeElements.Add(Node);
                }
                else
                {
                    foreach (CIM_Link term in this.InLinks)
                        if (term.TypeName == "cim:Terminal")
                        {
                            foreach (CIM_Link otherElem in term.Element.OutLinks)
                                if (!otherElem.Element.Equals(this) && (!NodeElements.Contains(otherElem.Element)))
                                    NodeElements.Add(otherElem.Element);
                        }
                    foreach (CIM_Link term in this.OutLinks)
                        if ((this.TypeName == "cim:TransformerWinding") && (term.TypeName == "cim:PowerTransformer"))
                            NodeElements.Add(term.Element);
                }
                return NodeElements.ToArray();
            }
        }

        /// <summary>
        /// Determine the stations connecting a line
        /// </summary>
        public CIM_Element[] LineStations
        {
            get
            {

                if (this.TypeName == "cim:ACLineSegment" || this.TypeName == "cim:SeriesCompensator")
                    return this.Parse("<cim:Terminal>cim:ConnectivityNode>cim:VoltageLevel>cim:Substation", null);
                else if (this.TypeName == "cim:Line")
                {
                    List<CIM_Element> OutElems = new List<CIM_Element>();
                    OutElems.AddRange(Parse("<cim:ACLineSegment<cim:Terminal>cim:ConnectivityNode>cim:VoltageLevel>cim:Substation", null));
                    OutElems.AddRange(Parse("<cim:SeriesCompensator<cim:Terminal>cim:ConnectivityNode>cim:VoltageLevel>cim:Substation", null));
                    return OutElems.ToArray();
                }
                else
                    throw new InvalidOperationException("Line stations can only be retrieved for Lines, ACLineSegments and SeriesCompensators!");
            }
        }

        /// <summary>Determine an element's TEID</summary>
        public virtual String TEID
        {
            get
            {
                if (Length == -1) return "";
                foreach (KeyValuePair<string, string> kvp in this.Attributes)
                    if (kvp.Key.EndsWith(".TEID", StringComparison.CurrentCultureIgnoreCase))
                        return kvp.Value;
                return string.Empty;
            }
        }


        /// <summary>
        /// Report the collection of PSSE bus numbers
        /// </summary>
        public string PSSEBusNumbers
        {
            get
            {
                List<int> Buses = new List<int>();
                foreach (CIM_Element Elem in ElementsOfType("etx:ConnectivityNodeGroup"))
                    Buses.Add(int.Parse(Elem["etx:ConnectivityNodeGroup.PSSEBusNumber"]));
                Buses.Sort();
                StringBuilder sB = new StringBuilder();
                foreach (int Bus in Buses)
                    sB.Append((sB.Length == 0 ? "" : ",") + Bus.ToString());
                return sB.ToString();
            }
        }

        /// <summary>
        /// Report the collection of PSSE bus names
        /// </summary>
        public string PSSEBusNames
        {
            get
            {
                List<string> Buses = new List<string>();
                foreach (CIM_Element Elem in ElementsOfType("etx:ConnectivityNodeGroup"))
                    Buses.Add(MM_Type_Converter.TitleCase(Elem["etx:ConnectivityNodeGroup.PSSEBusName"]));
                Buses.Sort();
                StringBuilder sB = new StringBuilder();
                foreach (string Bus in Buses)
                    sB.Append((sB.Length == 0 ? "" : ",") + Bus);
                return sB.ToString();
            }
        }

        /// <summary>
        /// Report the primary fuel type(s) of a unit or substation
        /// </summary>
        public String PrimaryFuelType
        {
            get
            {
                String PrimaryFuelType;
                if (TypeName.EndsWith("Unit"))
                    if (!Attributes.TryGetValue("etx:GeneratingUnit.PrimaryFuelType", out PrimaryFuelType))
                        PrimaryFuelType = "NA";
                    else
                        PrimaryFuelType = PrimaryFuelType.Substring(PrimaryFuelType.LastIndexOf('.') + 1);
                else if (TypeName == "cim:SynchronousMachine")
                    return ParseSingle("Unit", CIM).PrimaryFuelType;
                else if (TypeName == "cim:Substation")
                {
                    List<string> GenTypes = new List<string>();
                    foreach (CIM_Element Unit in Parse("etx:ResourceController<Unit", CIM))
                    {
                        String InVal = Unit.PrimaryFuelType;
                        if (!GenTypes.Contains(InVal))
                            GenTypes.Add(InVal);
                    }
                    GenTypes.Sort();
                    return String.Join(",", GenTypes.ToArray());
                }
                else
                    return "";

                if (PrimaryFuelType == "SUN")
                    return "Solar";
                else if (PrimaryFuelType == "WND")
                    return "Wind";
                else if (PrimaryFuelType == "WAT")
                    return "Hydro";
                else if (PrimaryFuelType == "NUC")
                    return "Nuclear";
                else if (PrimaryFuelType == "NG" || PrimaryFuelType == "BFG")
                    return "Gas";
                else if (PrimaryFuelType == "LIG" || PrimaryFuelType == "SUB" || PrimaryFuelType == "BIT")
                    return "Coal";
                else
                    return "Other";
            }
        }
    

        /// <summary>
        /// Report the voltage levels of a substation
        /// </summary>
        public String VoltageLevels
        {
            get
            {
                List<Single> Voltages = new List<float>();
                if (this is CIM_Trace)
                {
                    float Voltage;
                    foreach (CIM_Element Elem in Elements)
                        if (Elem.TypeName != "cim:PowerTransformer")
                        {
                            if (Elem.VoltageLevel != null && Single.TryParse(Elem.VoltageLevel.Name, out Voltage) && !Voltages.Contains(Voltage))
                                Voltages.Add(Voltage);
                        }
                        else
                            foreach (CIM_Element XFWinding in Elem.TransformerWinding)
                                if (XFWinding.VoltageLevel != null && Single.TryParse(XFWinding.VoltageLevel.Name, out Voltage) && !Voltages.Contains(Voltage))
                                    Voltages.Add(Voltage);
                }
                else
                    foreach (CIM_Element Elem in Parse("cim:VoltageLevel", null))
                        Voltages.Add(Single.Parse(Elem.Name));
                Voltages.Sort();
                StringBuilder sB = new StringBuilder();
                foreach (Single Voltage in Voltages)
                    sB.Append((sB.Length == 0 ? "" : ",") + Voltage.ToString());
                return sB.ToString();
            }
        }

        /// <summary>
        /// Return the voltage level for the current element, if possible.
        /// </summary>
        /// <returns></returns>
        public CIM_Element VoltageLevel
        {
            get
            {
                if (this.TypeName.EndsWith("Unit"))
                    return ParseSingle(">cim:SynchronousMachine>cim:VoltageLevel>cim:BaseVoltage", null);
                else if (this.TypeName == "cim:PowerTransformer")
                {
                    CIM_Element MaxLevel = null;
                    float MaxVoltage = float.MinValue;
                    foreach (CIM_Element XFw in this.TransformerWinding)
                    {
                        float Voltage = float.Parse(XFw.VoltageLevel["nominalVoltage"]);
                        if (Voltage > MaxVoltage)
                        {
                            Voltage = MaxVoltage;
                            MaxLevel = XFw.VoltageLevel;
                        }
                    }   
                    return MaxLevel;
                }
                else if (this.TypeName == "cim:TransformerWinding")
                    return ParseSingle(">cim:BaseVoltage", null);
                else if (this.TypeName == "etx:EPSMeter")
                    return ParseSingle(">etx:PricingVector>cim:Terminal>cim:ConnectivityNode>cim:VoltageLevel>cim:BaseVoltage", null);
                else if (this.TypeName == "etx:PricingVector")
                    return ParseSingle(">cim:Terminal>cim:ConnectivityNode>cim:VoltageLevel>cim:BaseVoltage", null);
                else if (this.TypeName == "cim:Line")
                {
                    CIM_Element FoundElem = ParseSingle(">cim:ACLineSegment>cim:VoltageLevel>cim:BaseVoltage", null);
                    if (FoundElem != null)
                        return FoundElem;
                    else
                        return ParseSingle(">cim:SeriesCompensator>cim:VoltageLevel>cim:BaseVoltage", null);
                }
                else
                {
                    CIM_Element FoundElem = ParseSingle(">cim:Bay>cim:VoltageLevel>cim:BaseVoltage", null);
                    if (FoundElem != null)
                        return FoundElem;
                    else if ((FoundElem = ParseSingle(">cim:VoltageLevel>cim:BaseVoltage", null)) != null)
                        return FoundElem;
                    else
                        return ParseSingle(">cim:BaseVoltage", null);
                }
            }
        }
        #endregion

        #region Identity
        /// <summary>
        /// Report an easy-to-read string 
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            if (TypeName == "cim:ACLineSegment" || TypeName == "cim:SeriesCompensator")
            {
                CIM_Element[] Segs = ACSegmentStations;
                CIM_Element ParentLine = ParseSingle("cim:Line", null);
                if (Segs[0].Equals(Segs[1]))
                    return this.TypeName.Split(':')[1] + " " + MM_Type_Converter.TitleCase(ParentLine.Name + (ParentLine.InLinks.Length == 1 ? "" : Name));
                else
                    return this.TypeName.Split(':')[1] + " " + MM_Type_Converter.TitleCase(ParentLine.Name + (ParentLine.InLinks.Length == 1 ? "" : Name + " (" + Segs[0].Name + " - " + Segs[1].Name + ")"));
            }

            CIM_Element Sub = Substation;
            //CIM_Element Voltage = VoltageLevel;
            //(Voltage == null ? "" : Voltage.Name + " ") + TypeName.Substring(TypeName.IndexOf(':') + 1) + " " + 
            return this.TypeName.Split(':')[1] + " " + (Sub == null || Sub == this ? "" : Sub.Name + ".") + this.Name;
        }

        /// <summary>
        /// Present a formatted representation of the element
        /// </summary>
        /// <param name="format"></param>
        /// <param name="formatProvider"></param>
        /// <returns></returns>
        public string ToString(string format, IFormatProvider formatProvider)
        {

            if (String.IsNullOrEmpty(format))
                return this.ToString();
            else
                return this[format];
        }

        /// <summary>
        /// Return the parent element for the current element
        /// </summary>
        public CIM_Element ParentElement
        {
            get
            {
                Dictionary<String, String> Attr = this.Attributes;
                XmlElement xType = MM_Database_Model.xDataDictionary.SelectSingleNode("XDF/Types/Type[@RdfName='" + TypeName.Substring(TypeName.IndexOf(':') + 1) + "']") as XmlElement;
                if (xType == null)
                    return null;
                //Now, try and find an association                
                do
                {
                    foreach (XmlElement xAssoc in MM_Database_Model.xDataDictionary.SelectNodes("XDF/Associations/Association[@TypeA='" + xType.Attributes["Name"].Value + "' and @AssociationType='ParentChild']"))
                    {
                        String TestVal;
                        if (Attr.TryGetValue(xAssoc.Attributes["Namespace"].Value + ":" + xAssoc.Attributes["TypeA"].Value + "." + xAssoc.Attributes["RdfRoleB"].Value, out TestVal))
                            return CIM.FromRdfID(TestVal.Substring(1));
                    }
                    xType = MM_Database_Model.xDataDictionary.SelectSingleNode("XDF/Types/Type[@RdfName='" + xType.Attributes["BaseType"].Value + "']") as XmlElement;
                }
                while (xType != null);
                return null;
            }
        }

        /// <summary>
        /// Report the list of elements marked as a child
        /// </summary>
        public CIM_Element[] ChildElements
        {
            get
            {
                XmlElement xType = MM_Database_Model.xDataDictionary.SelectSingleNode("XDF/Types/Type[@RdfName='" + TypeName.Substring(TypeName.IndexOf(':') + 1) + "']") as XmlElement;
                Dictionary<String, List<String>> AttributesToCheck = new Dictionary<string, List<string>>();
                List<String> OutAttr;

                //Go through our list of attributes, and add it in to our array
                if (xType != null)
                    do
                    {
                        foreach (XmlElement xAssoc in MM_Database_Model.xDataDictionary.SelectNodes("XDF/Associations/Association[@TypeA='" + xType.Attributes["Name"].Value + "' and @AssociationType='ParentChild']"))
                        {
                            if (!AttributesToCheck.TryGetValue(xAssoc.Attributes["Namespace"].Value + ":" + xAssoc.Attributes["TypeB"].Value, out OutAttr))
                                AttributesToCheck.Add(xAssoc.Attributes["Namespace"].Value + ":" + xAssoc.Attributes["TypeB"].Value, OutAttr = new List<string>(5));
                            OutAttr.Add(xAssoc.Attributes["Namespace"].Value + ":" + xAssoc.Attributes["TypeA"].Value + "." + xAssoc.Attributes["RdfRoleA"].Value);
                        }
                        xType = MM_Database_Model.xDataDictionary.SelectSingleNode("XDF/Types/Type[@RdfName='" + xType.Attributes["BaseType"].Value + "']") as XmlElement;
                    }
                    while (xType != null);
                        
                //Now, look at our incoming links, see if we can find anything
                List<CIM_Element> OutElements = new List<CIM_Element>();
                foreach (CIM_Link InLink in InLinks)
                    if (AttributesToCheck.TryGetValue(InLink.TypeName, out OutAttr))
                        foreach (KeyValuePair<String, String> kvp in InLink.Element.AttributeList)
                            if (OutAttr.Contains(kvp.Key))
                                OutElements.Add(CIM.FromRdfID(kvp.Value.Substring(1)));
                        
                        
                        
                return OutElements.ToArray();
            }
        }


        /// <summary>
        /// Report the full element path
        /// </summary>
        public virtual string ElementPath
        {
            get
            {
                try
                {
                    Stack<String> Names = new Stack<string>();
                    CIM_Element Elem = this;
                    CIM_Element PrevElem = this;
                    while (Elem != null)
                    {
                        Names.Push(Elem.Name);
                        PrevElem = Elem;
                        Elem = Elem.ParentElement;
                    }
                    if (PrevElem.TypeName.EndsWith("sysorphan", StringComparison.CurrentCultureIgnoreCase))
                        Names.Push(PrevElem.ElemType);
                    return String.Join("\\", Names.ToArray());
                }
                catch (Exception)
                {
                    return "Redacted\\RDCTD-" + this.rdfID.ToString();
                }
            }
        }

        #endregion

        #region Comparisons
        /// <summary>
        /// Determine whether two CIM elements are identical
        /// </summary>
        /// <param name="other">The other object</param>
        /// <returns></returns>
        public int CompareTo(object other)
        {
            if (other is CIM_Element)
                return rdfID.CompareTo((other as CIM_Element).rdfID);
            else
                return rdfID.ToString().CompareTo(other.ToString());
        }

        /// <summary>
        /// Determine whether two CIM elements are identical
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public int CompareTo(CIM_Element other)
        {
            return rdfID.CompareTo(other.rdfID);
        }

        /// <summary>
        /// Determine whether two CIM elements are equal
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool Equals(CIM_Element other)
        {
            if (other == null)
                return false;
            else
                return rdfID.Equals(other.rdfID);
        }

        /// <summary>
        /// Determine whether two CIM elements are equal
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            if (obj is CIM_Element == false)
                return false;
            else
                return rdfID.Equals((obj as CIM_Element).rdfID);
        }

        /// <summary>
        /// Report the hash code for the rdf:ID
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return rdfID.GetHashCode();
        }
        #endregion

        #region Menu handling
        /// <summary>
        /// Get popup information for our menu item
        /// </summary>
        /// <returns></returns>
        public String GetPopupText()
        {
            StringBuilder sB = new StringBuilder();
            Int64 TryParse;
            
            if (VoltageLevel != null)
                if (TypeName == "cim:Breaker" || TypeName == "cim:Disconnector")
                    sB.AppendLine(MM_Type_Converter.TitleCase(LongName) + " (" + VoltageLevel.Name + " " + ElemType + "): " + (this["cim:Switch.normalOpen"] == "true" ? "Normal OPEN": "Normal CLOSED"));
                else                                    
                    sB.AppendLine(MM_Type_Converter.TitleCase(LongName) + " (" + VoltageLevel.Name + " " + ElemType + ")");
            else
                sB.AppendLine(MM_Type_Converter.TitleCase(LongName) + " (" + ElemType + ")");

            //sB.AppendLine("   " + ElementPath);
            if (Substation != null && this.TypeName != "cim:Substation")
            {
                if (String.IsNullOrEmpty(Substation.LongName) || LongName == Substation.Name)
                    sB.AppendLine("  In substation " + MM_Type_Converter.TitleCase(Substation.Name));
                else
                    sB.AppendLine("  In substation " + MM_Type_Converter.TitleCase(Substation.LongName) + " (" + MM_Type_Converter.TitleCase(Substation.Name) + ")");
            }

            if (!String.IsNullOrEmpty(TEID))
                sB.AppendLine("  TEID: " + Convert.ToUInt64(TEID).ToString("#,##0"));

            //If we're a node, add in our node information
            CIM_Element NodeGroup;
            if (TypeName == "cim:ConnectivityNode" && (NodeGroup = this.ParseSingle("etx:ConnectivityNodeGroup", CIM)) != null)
            {
                //sB.AppendLine("  NodeGroup: " + MM_Type_Converter.TitleCase(NodeGroup.Name));
                sB.AppendLine("  PSSE Number: " + NodeGroup["etx:ConnectivityNodeGroup.PSSEBusNumber"]);
                sB.AppendLine("  PSSE Name: " + MM_Type_Converter.TitleCase(NodeGroup["etx:ConnectivityNodeGroup.PSSEBusName"]));
                CIM_Element ResourceNode = ParseSingle("etx:ElectricalBus<etx:ResourceNode", CIM);
                if (ResourceNode != null)
                    sB.AppendLine("  Resource Node: " + MM_Type_Converter.TitleCase(ResourceNode.Name));                    
            }


            Dictionary<CIM_Element, float> ResourceOperators = this.ResourceOperators;
            Dictionary<CIM_Element, float> ResourceOwners = this.ResourceOwners;

            if (ResourceOperators.Count == 0 && Substation != null && Substation.Operator != null)
                ResourceOperators.Add(Substation.Operator, 100);
            if (ResourceOwners.Count == 0 && Substation != null && Substation.Owner != null)
                ResourceOperators.Add(Substation.Owner, 100);

            bool CompleteMatch = true;
            float TryVal;
            foreach (KeyValuePair<CIM_Element, float> kvp in ResourceOperators)
                if (!ResourceOwners.TryGetValue(kvp.Key, out TryVal) || kvp.Value != TryVal)
                    CompleteMatch = false;
            foreach (KeyValuePair<CIM_Element, float> kvp in ResourceOwners)
                if (!ResourceOperators.TryGetValue(kvp.Key, out TryVal) || kvp.Value != TryVal)
                    CompleteMatch = false;


            if (ResourceOwners.Count == 0)
            { }
            else if (CompleteMatch)
            {
                sB.AppendLine("-");
                foreach (KeyValuePair<CIM_Element, float> kvp in ResourceOwners)
                {
                    sB.AppendLine("Owner/Operator: " + kvp.Key["cim:IdentifiedObject.aliasName"] + " / " + MM_Type_Converter.TitleCase(kvp.Key.Name) + (kvp.Value == 100 ? "" : "  (" + kvp.Value.ToString("0") + "%)"));
                    Dictionary<String, String> Attr = kvp.Key.Attributes;
                    if (WritePhone)
                        if (Int64.TryParse(kvp.Key["PrimaryPhone"], out TryParse))
                            sB.AppendLine("  Phone: " + TryParse.ToString("(000) 000-0000"));
                        else
                            sB.AppendLine("  Phone: " + kvp.Key["PrimaryPhone"]);
                    if (WriteDUNS)
                        if (Int64.TryParse(kvp.Key["DUNS"], out TryParse))
                            sB.AppendLine("  DUNS: " + TryParse.ToString("#,##0"));
                        else
                            sB.AppendLine("  DUNS: " + kvp.Key["DUNS"]);
                }
            }
            else
            {
                sB.AppendLine("-");
                for (int a = 0; a < 2; a++)
                {
                    Dictionary<CIM_Element, float> DicToParse = a == 0 ? ResourceOwners : ResourceOperators;
                    foreach (KeyValuePair<CIM_Element, float> kvp in DicToParse)
                    {
                        sB.AppendLine((a == 0 ? "Owner: " : "Operator: ") + kvp.Key["cim:IdentifiedObject.aliasName"] + " / " + MM_Type_Converter.TitleCase(kvp.Key.Name) + (kvp.Value == 100 ? "" : " (" + kvp.Value.ToString("0") + "%)"));
                        Dictionary<String, String> Attr = kvp.Key.Attributes;
                        if (WritePhone)
                            if (Int64.TryParse(kvp.Key["PrimaryPhone"], out TryParse))
                                sB.AppendLine("  Phone: " + TryParse.ToString("(000) 000-0000"));
                            else
                                sB.AppendLine("  Phone: " + kvp.Key["PrimaryPhone"]);
                        if (WriteDUNS)
                            if (Int64.TryParse(kvp.Key["DUNS"], out TryParse))
                                sB.AppendLine("  DUNS: " + TryParse.ToString("#,##0"));
                            else
                                sB.AppendLine("  DUNS: " + kvp.Key["DUNS"]);
                    }
                }
            }



            //Now, pull in our contingency information
            if (CIM.Traces != null)
            {
                List<String> Traces = new List<string>();
                foreach (CIM_Trace Trace in CIM.Traces.Values)
                    if (Array.IndexOf(Trace.rdfIDs, rdfID) != -1 && !Trace.Name.StartsWith("N_"))
                        Traces.Add("  " + Trace.Name + " (" + Trace.Description + ")");
                Traces.Sort();

                if (Traces.Count > 0)
                {
                    sB.AppendLine("-");
                    sB.AppendLine("Contingencies:");
                    foreach (String str in Traces)
                        sB.AppendLine(str);
                }
            }
            return sB.ToString();
        }


        /// <summary>
        /// Write out information on our current CIM element.
        /// </summary>
        /// <param name="cms"></param>
        /// <param name="MenuObject"></param>
        public void WriteMenuElement(ToolStripItemCollection cms, Object MenuObject)
        {

            if (VoltageLevel != null)
                (cms.Add(MM_Type_Converter.TitleCase(Name) + " (" + VoltageLevel.Name + " " + TypeName.Split(':')[1] + ")") as ToolStripMenuItem).Enabled = false;
            else
                (cms.Add(MM_Type_Converter.TitleCase(Name) + " (" + TypeName.Split(':')[1] + ")") as ToolStripMenuItem).Enabled = false;

            cms.Add("   " + ElementPath);
            if (Substation != null && this.TypeName != "cim:Substation")
            {
                String LongName = Substation["cim:IdentifiedObject.description"];
                if (String.IsNullOrEmpty(LongName) || LongName == Substation.Name)
                    (cms.Add("  In substation " + MM_Type_Converter.TitleCase(Substation.Name)) as ToolStripMenuItem).Enabled = false;
                else
                    (cms.Add("  In substation " + MM_Type_Converter.TitleCase(LongName) + " (" + MM_Type_Converter.TitleCase(Substation.Name) + ")") as ToolStripMenuItem).Enabled = false;
            }
            else if (Substation == null && this.TypeName == "cim:ACLineSegment")
            (cms.Add("  Between " + this.LineStations[0].LongName + " (" + this.LineStations[0].Name + ") and " + LineStations[1].LongName + " (" + LineStations[1].Name + ")") as ToolStripMenuItem).Enabled = false;
                
            if (!String.IsNullOrEmpty(TEID))
                (cms.Add("TEID: " + Convert.ToUInt64(TEID).ToString("#,##0")) as ToolStripMenuItem).Enabled = false;


            //Add our MM objects
            cms.Add("-");
            cms.Add("&Zoom/pan Macomber Map");
            cms.Add("&Property page (MM)");
            cms.Add("&One-Line display (MM)");



            if (MenuObject is ContextMenuStrip)
                (MenuObject as ContextMenuStrip).ItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
            else if (MenuObject is ToolStripDropDownItem)
                (MenuObject as ToolStripDropDownItem).DropDownOpening += NewItem_DropDownOpening;

            cms.Add("-");
            if (InLinks.Length > 0)
            {
                ToolStripMenuItem NewItem = (ToolStripMenuItem)cms.Add("Incoming (" + InLinks.Length.ToString("#,##0") + ")");
                NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                NewItem.Tag = InLinks;
                NewItem.DropDownItems.Add("");
            }

            if (OutLinks.Length > 0)
            {
                ToolStripMenuItem NewItem = (ToolStripMenuItem)cms.Add("Outgoing (" + OutLinks.Length.ToString("#,##0") + ")");
                NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                NewItem.Tag = OutLinks;
                NewItem.DropDownItems.Add("");
            }

            {
                Dictionary<String, String> OutAttr = Attributes;
                ToolStripMenuItem NewItem = (ToolStripMenuItem)cms.Add("Attributes (" + OutAttr.Count.ToString("#,##0") + ")");
                NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                NewItem.Tag = OutAttr;
                NewItem.DropDownItems.Add("");
            }

            List<CIM_Element> OutElems = new List<CIM_Element>();
            try { OutElems.AddRange(Elements); }
            catch (Exception) { }
            try { OutElems.AddRange(Nodes); }
            catch (Exception) { }
            if (OutElems.Count > 0)
            {
                cms.Add("-");
                ToolStripMenuItem NewItem = (ToolStripMenuItem)cms.Add("Elements (" + OutElems.Count.ToString("#,##0") + ")");
                NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                NewItem.Tag = OutElems.ToArray();
                NewItem.DropDownItems.Add("");
            }

            CIM_Element[] SPSs = SPSorRAPs;
            if (SPSs.Length > 0)
            {
                cms.Add("-");
                ToolStripMenuItem NewItem = (ToolStripMenuItem)cms.Add("SPS/RAPs (" + SPSs.Length.ToString("#,##0") + ")");
                NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                NewItem.Tag = SPSs;
                NewItem.DropDownItems.Add("");
            }



            //Add ownership/operatorship informaiton
            Int64 TryParse;
            if (Owner == null && Operator == null)
            { }
            else if (Owner.Equals(Operator))
            {
                cms.Add("-");
                cms.Add("Owner/Operator: " + MM_Type_Converter.TitleCase(Owner.Name)).Enabled = false;
                if (Int64.TryParse(Owner["PrimaryPhone"], out TryParse))
                    cms.Add("  Phone: " + TryParse.ToString("(000) 000-0000")).Enabled = false;
                else
                    cms.Add("  Phone: " + Owner["PrimaryPhone"]).Enabled = false;
                if (Int64.TryParse(Owner["DUNS"], out TryParse))
                    cms.Add("  DUNS: " + TryParse.ToString("#,##0")).Enabled = false;
                else
                    cms.Add("  DUNS: " + Owner["DUNS"]).Enabled = false;
            }
            else
            {
                cms.Add("-");
                cms.Add("Owner: " + MM_Type_Converter.TitleCase(Owner.Name)).Enabled = false;
                if (Int64.TryParse(Owner["PrimaryPhone"], out TryParse))
                    cms.Add("  Phone: " + TryParse.ToString("(000) 000-0000")).Enabled = false;
                else
                    cms.Add("  Phone: " + Owner["PrimaryPhone"]).Enabled = false;
                if (Int64.TryParse(Owner["DUNS"], out TryParse))
                    cms.Add("  DUNS: " + TryParse.ToString("#,##0")).Enabled = false;
                else
                    cms.Add("  DUNS: " + Owner["DUNS"]).Enabled = false;
                cms.Add("");
                cms.Add("Operator: " + MM_Type_Converter.TitleCase(Operator.Name)).Enabled = false;
                if (Int64.TryParse(Operator["PrimaryPhone"], out TryParse))
                    cms.Add("  Phone: " + TryParse.ToString("(000) 000-0000")).Enabled = false;
                else
                    cms.Add("  Phone: " + Operator["PrimaryPhone"]).Enabled = false;
                if (Int64.TryParse(Operator["DUNS"], out TryParse))
                    cms.Add("  DUNS: " + TryParse.ToString("#,##0")).Enabled = false;
                else
                    cms.Add("  DUNS: " + Operator["DUNS"]).Enabled = false;
            }

            if (!String.IsNullOrEmpty(TEID))
                CIM.AddContingencyMenuItems(cms, this);

            MenuObject.GetType().GetProperty("Tag").SetValue(MenuObject, this, null);
        }

        /// <summary>
        /// Handle MM dropdown click selection
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MM_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {

        }

        /// <summary>
        /// Handle a new dropdown item opening
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void NewItem_DropDownOpening(object sender, EventArgs e)
        {
            ToolStripDropDownItem DropDownMenu = (ToolStripDropDownItem)sender;
            DropDownMenu.DropDownItems.Clear();
            DropDownMenu.DropDownItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
            if (DropDownMenu.Tag is CIM_Link[])
            {
                AddDropDownItems(DropDownMenu.Tag as CIM_Link[], DropDownMenu);
                DropDownMenu.DropDownItems.Add("-");
                DropDownMenu.DropDownItems.Add("Add all");
            }
            else if (DropDownMenu.Tag is CIM_Element[])
            {
                AddDropDownItems(DropDownMenu.Tag as CIM_Element[], DropDownMenu);
                DropDownMenu.DropDownItems.Add("-");
                DropDownMenu.DropDownItems.Add("Add all");
            }
            else if (DropDownMenu.Tag is CIM_Link)
                (DropDownMenu.Tag as CIM_Link).Element.WriteMenuElement(DropDownMenu.DropDownItems, DropDownMenu);
            else if (DropDownMenu.Tag is CIM_Element)
                (DropDownMenu.Tag as CIM_Element).WriteMenuElement(DropDownMenu.DropDownItems, DropDownMenu);
            else if (DropDownMenu.Tag is Dictionary<String, String>)
                AddDropDownItems(DropDownMenu.Tag as Dictionary<String, String>, DropDownMenu);
        }



        /// <summary>
        /// Add all appropriate drop-down menu elements
        /// </summary>
        /// <param name="Elements">The collection of elements that fit our parameters</param>
        /// <param name="DropDownMenu">The drop down menu to receive the new lines</param>        
        private void AddDropDownItems(CIM_Link[] Elements, ToolStripDropDownItem DropDownMenu)
        {

            //Based on our depth, allocate results accordingly.                                                           
            int CurDepth = GetDepth(DropDownMenu);
            SortedDictionary<String, List<CIM_Link>> Elems = new SortedDictionary<string, List<CIM_Link>>();

            //Go through all elements, and add the appropriate ones into our dictionary
            foreach (CIM_Link ElemLink in Elements)
            {
                CIM_Element Elem = ElemLink.Element;
                String[] splType = Elem.TypeName.Split('.', ':');
                if (CurDepth > splType.GetUpperBound(0))
                {
                    ToolStripMenuItem NewItem = (ToolStripMenuItem)DropDownMenu.DropDownItems.Add(Elem.rdfID.ToString() + " (" + Elem.Name + ")");
                    NewItem.Tag = Elem;
                    NewItem.DropDownItems.Add("");
                    NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                    NewItem.DropDownItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
                }
                else if (CurDepth == 1)
                {
                    if (!Elems.ContainsKey(splType[0] + ":" + splType[1]))
                        Elems.Add(splType[0] + ":" + splType[1], new List<CIM_Link>(10));
                    Elems[splType[0] + ":" + splType[1]].Add(ElemLink);
                }
                else
                {
                    if (!Elems.ContainsKey(splType[CurDepth]))
                        Elems.Add(splType[CurDepth], new List<CIM_Link>(10));
                    Elems[splType[CurDepth]].Add(ElemLink);
                }
            }

            foreach (KeyValuePair<String, List<CIM_Link>> Elem in Elems)
            {
                ToolStripMenuItem NewItem = (ToolStripMenuItem)DropDownMenu.DropDownItems.Add(Elem.Key + (Elem.Value.Count > 0 ? " (" + Elem.Value.Count.ToString("#,##0") + ")" : ""));
                NewItem.Tag = Elem.Value.ToArray();
                NewItem.DropDownItems.Add("");
                NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                NewItem.DropDownItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
            }
        }

        /// <summary>
        /// Add all appropriate drop-down menu elements
        /// </summary>
        /// <param name="Elements">The collection of elements that fit our parameters</param>
        /// <param name="DropDownMenu">The drop down menu to receive the new lines</param>        
        private void AddDropDownItems(CIM_Element[] Elements, ToolStripDropDownItem DropDownMenu)
        {

            //Based on our depth, allocate results accordingly.                                                           
            int CurDepth = GetDepth(DropDownMenu);
            SortedDictionary<String, List<CIM_Element>> Elems = new SortedDictionary<string, List<CIM_Element>>();

            //Go through all elements, and add the appropriate ones into our dictionary
            foreach (CIM_Element Elem in Elements)
            {
                String[] splType = Elem.TypeName.Split('.', ':');
                if (CurDepth > splType.GetUpperBound(0))
                {
                    ToolStripMenuItem NewItem = (ToolStripMenuItem)DropDownMenu.DropDownItems.Add(Elem.rdfID.ToString() + " (" + Elem.Name + ")");
                    NewItem.Tag = Elem;
                    NewItem.DropDownItems.Add("");
                    NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                    NewItem.DropDownItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
                }
                else if (CurDepth == 1)
                {
                    if (!Elems.ContainsKey(splType[0] + ":" + splType[1]))
                        Elems.Add(splType[0] + ":" + splType[1], new List<CIM_Element>(10));
                    Elems[splType[0] + ":" + splType[1]].Add(Elem);
                }
                else
                {
                    if (!Elems.ContainsKey(splType[CurDepth]))
                        Elems.Add(splType[CurDepth], new List<CIM_Element>(10));
                    Elems[splType[CurDepth]].Add(Elem);
                }
            }

            foreach (KeyValuePair<String, List<CIM_Element>> Elem in Elems)
            {
                ToolStripMenuItem NewItem = (ToolStripMenuItem)DropDownMenu.DropDownItems.Add(Elem.Key + (Elem.Value.Count > 0 ? " (" + Elem.Value.Count.ToString("#,##0") + ")" : ""));
                NewItem.Tag = Elem.Value.ToArray();
                NewItem.DropDownItems.Add("");
                NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                NewItem.DropDownItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
            }
        }

        /// <summary>
        /// Add all appropriate drop-down menu elements
        /// </summary>
        /// <param name="Elements">The collection of elements that fit our parameters</param>
        /// <param name="DropDownMenu">The drop down menu to receive the new lines</param>        
        private void AddDropDownItems(Dictionary<String, String> Elements, ToolStripDropDownItem DropDownMenu)
        {

            //Based on our depth, allocate results accordingly.
            int CurDepth = GetDepth(DropDownMenu);
            SortedDictionary<String, Dictionary<String, String>> Elems = new SortedDictionary<string, Dictionary<String, String>>();

            //Go through all elements, and add the appropriate ones into our dictionary
            foreach (KeyValuePair<String, String> Elem in Elements)
            {
                String[] splType = Elem.Key.Split('.', ':');
                if (CurDepth == splType.GetUpperBound(0))
                {
                    ToolStripMenuItem NewItem = (ToolStripMenuItem)DropDownMenu.DropDownItems.Add(splType[CurDepth] + " = " + Elem.Value);
                    NewItem.DropDownItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
                    if (Elem.Value.Length > 0 && Elem.Value[0] == '#')
                    {
                        CIM_Element ElemToLink = CIM.FromRdfID(Elem.Value.Substring(1));
                        NewItem.Tag = ElemToLink;
                    }
                    else
                        NewItem.Enabled = false;
                    NewItem.Tag = Elem;
                }
                else if (CurDepth == 1 && splType.Length > 1)
                {
                    if (!Elems.ContainsKey(splType[0] + ":" + splType[1]))
                        Elems.Add(splType[0] + ":" + splType[1], new Dictionary<String, String>(10));
                    Elems[splType[0] + ":" + splType[1]].Add(Elem.Key, Elem.Value);
                }
                else if (CurDepth > splType.Length)
                {
                    if (!Elems.ContainsKey(splType[CurDepth]))
                        Elems.Add(splType[CurDepth], new Dictionary<String, String>(10));
                    Elems[splType[CurDepth]].Add(Elem.Key, Elem.Value);
                }
            }

            foreach (KeyValuePair<String, Dictionary<String, String>> Elem in Elems)
            {
                ToolStripMenuItem NewItem = (ToolStripMenuItem)DropDownMenu.DropDownItems.Add(Elem.Key + (Elem.Value.Count > 0 ? " (" + Elem.Value.Count.ToString("#,##0") + ")" : ""));
                NewItem.Tag = Elem.Value;
                NewItem.DropDownItems.Add("");
                NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                NewItem.DropDownItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
            }

        }

        /// <summary>
        /// Handle the clicking of a menu item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cms_ItemClicked(Object sender, ToolStripItemClickedEventArgs e)
        {
            //If we have an MM command, process it
            if (e.ClickedItem.Text == "&Zoom/pan Macomber Map" || e.ClickedItem.Text == "&Property page (MM)" || e.ClickedItem.Text == "&One-Line display (MM)")
            {
                List<String> OutParams = new List<string>();
                OutParams.Add(e.ClickedItem.Text.Replace("-", "").Split(" /&".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)[0]);
                if (TypeName == "cim:SynchronousMachine")
                    OutParams.Add("Unit");
                else
                    OutParams.Add(TypeName.Split(':')[1]);
                if (this.Substation != null && this.TypeName != "cim:Substation")
                    OutParams.Add(this.Substation.Name + "." + this.Name);
                else
                    OutParams.Add(this.Name);

                MM_System_Interface.SendMessage(MM_System_Interface.MacomberMap, OutParams.ToArray());
                return;
            }




            //Determine our highest-parent menu
            ToolStripItem DropDownMenu = (sender as ToolStripItem);
            if (DropDownMenu == null)
                return;
            //            else
            //              while (DropDownMenu.OwnerItem != null)

            if (e.ClickedItem.Tag is CIM_Element)
                MM_Database_Model.DisplayElement(e.ClickedItem.Tag as CIM_Element);
            else if (e.ClickedItem.Tag is CIM_Link)
                MM_Database_Model.DisplayElement((e.ClickedItem.Tag as CIM_Link).Element);
            else if (e.ClickedItem.Tag is KeyValuePair<CIM_Trace, UInt64>)
            {
                //TODO: Repair loading the contingency one-line
                /* KeyValuePair<CIM_Contingency, UInt64> kvp = (KeyValuePair<CIM_Contingency, UInt64>)e.ClickedItem.Tag;
                 FileInfo[] fI = new DirectoryInfo(CIM.ContingencyDirectory).GetFiles(kvp.Key.Name + ".jpg");
                 if (fI.Length == 0)
                     MessageBox.Show("Unable to locate a one-line image for contingency " + kvp.Key.Name, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                 else
                     CIM.DisplayOneLine(kvp.Key, fI[0].FullName, kvp.Value);*/
            }
        }

        /// <summary>
        /// Determine the depth of a menu item
        /// </summary>
        /// <param name="DropDownMenu"></param>
        /// <returns></returns>
        private int GetDepth(ToolStripItem DropDownMenu)
        {
            int Depth = 0;
            do
            {
                Depth++;
                DropDownMenu = DropDownMenu.OwnerItem;
                if (DropDownMenu == null || DropDownMenu.Tag == null || DropDownMenu.Tag is CIM_Element || DropDownMenu.Tag is CIM_Link)
                    return Depth;
            } while (true);
        }

        #endregion

        /// <summary>
        /// For a power transformer, return all connected transformer windings
        /// </summary>
        public CIM_Element[] TransformerWinding
        {
            get
            {
                
                if (TypeName == "cim:PowerTransformer")
                    return Parse("cim:TransformerWinding", CIM);
                else if (TypeName == "etx:TransformerKluge")
                    return Parse("<cim:PowerTransformer<cim:TransformerWinding", CIM);
                else if (TypeName == "cim:Substation")
                    return Parse("<cim:PowerTransformer<cim:TransformerWinding", CIM);
                else
                    throw new InvalidDataException("Unable to get transformer windings except on power transformers!");                
            }
        }
    }
}